# 导入相关库
import numpy as np
import matplotlib.pyplot as plt

# 1.散点输入
points = np.array([
    [0.8, 0],
    [1.1, 0],
    [1.7, 0],
    [1.9, 0],
    [2.7, 1],
    [3.2, 1],
    [3.7, 1],
    [4.0, 1],
    [5.0, 0],
    [5.5, 0],
    [6.0, 0],
    [6.3, 0]
])

# 将输入样本中的特征和标签分开
X = points[:, 0]    # 获取第一列的所有行数据
y = points[:, 1]    # 获取第二列的所有行数据

def sigmoid(x):
    """
    sigmoid公式 =>
                  1
        σ(x) = ------------
                1 + e^(-x)
    转换成代码:
        σ(x) = 1 / (1 + exp(-x))
    """
    return 1 / (1 + np.exp(-x))

# 2.前向计算
def forward(w11_1, b1_1, w12_1, b2_1, w11_2, w21_2, b1_2, X):
    z1_1 = w11_1 * X + b1_1
    a1_1 = sigmoid(z1_1)

    z2_1 = w12_1 * X + b2_1
    a2_1 = sigmoid(z2_1)

    z1_2 = w11_2 * a1_1 + w21_2 * a2_1 + b1_2
    a1_2 = sigmoid(z1_2)
    return a1_1, a2_1, a1_2


# 3.参数初始化
w11_1, b1_1, w12_1, b2_1, w11_2, w21_2, b1_2 = 0.1, 0.6, 0.9, 0, -1.5, 0.1, 0.9
lr = 0.5

# 4.损失函数
def loss_func(y, y_hat):
    """
    公式:
        MSE = (1/n) * Σ(y - y^)^2
    """
    loss = np.mean((y - y_hat) ** 2)
    return loss

# 从0~7的100个点,作为测试输入
x_values = np.linspace(0, 7, 100)
loss_list = []  # 存储的是每一次loss的变化
# 5.开始迭代
epoches = 5000
for epoch in range(1, epoches + 1):
    # 计算损失函数
    a1_1, a2_1, a1_2 = forward(w11_1, b1_1, w12_1, b2_1, w11_2, w21_2, b1_2, X)
    loss = loss_func(y, a1_2)
    # 查看loss
    loss_list.append(loss)

    # 6.反向传播 => 一定要看图
    # 损失函数对a1-2的导数
    deda1_2 = -2 * (y - a1_2)
    # 损失函数对z1_2的导数
    dedz1_2 = deda1_2 * a1_2 * (1 - a1_2)
    # 损失函数对w11_2的导数
    dedw11_2 = np.mean(dedz1_2 * a1_1)
    # 损失函数对w21_2的导数
    dedw21_2 = np.mean(dedz1_2 * a2_1)
    # 损失函数对b1_2的导数
    dedb1_2 = np.mean(dedz1_2)
    # 损失函数对a1_1的导数
    deda1_1 = dedz1_2 * w11_2
    # 损失函数对z1_1的导数
    dedz1_1 = deda1_1 * a1_1 * (1 - a1_1)
    # 损失函数对w11_1的导数
    dedw11_1 = np.mean(dedz1_1 * X)
    # 损失函数对b1_1的导数
    dedb1_1 = np.mean(dedz1_1)
    # 损失函数对a2_1的导数
    deda2_1 = dedz1_2 * w21_2
    # 损失函数对z2_1的导数
    dedz2_1 = deda2_1 * a2_1 * (1 - a2_1)
    # 损失函数对w12_1的导数
    dedw12_1 = np.mean(dedz2_1 * X)
    # 损失函数对b2_1的导数
    dedb2_1 = np.mean(dedz2_1)

    # 梯度下降获得更新 w新 = w旧 - 学习率 * w导
    w11_2 -= lr * dedw11_2
    w21_2 -= lr * dedw21_2
    b1_2 -= lr * dedb1_2

    w11_1 -= lr * dedw11_1
    b1_1 -= lr * dedb1_1
    w12_1 -= lr * dedw12_1
    b2_1 -= lr * dedb2_1
    # 7.显示频率设备
    if epoch == 1 or epoch % 100 == 0:
        print(f"epoch: {epoch}, loss: {loss}")
        # 测试输入的预测值
        _, _, y_values = forward(w11_1, b1_1, w12_1, b2_1, w11_2, w21_2, b1_2, x_values)
        # 8.梯度下降显示
        # 清除当前图形窗口中的所有内容，准备绘制新图形
        plt.clf()
        # 创建2行1列的子图网格，并选择第1个子图（顶部子图）
        plt.subplot(2, 1, 1)
        # 绘制x_values和y_values的折线图（通常是拟合曲线）
        plt.plot(x_values, y_values)
        # 在同一个子图上绘制原始数据点(X, y)的散点图
        plt.scatter(X, y)
        # 选择第2个子图（底部子图）
        plt.subplot(2, 1, 2)
        # 绘制损失值随迭代次数的变化曲线
        # range(len(loss_list))生成迭代次数的x轴坐标
        # loss_list包含每次迭代的损失值
        plt.plot(range(len(loss_list)), loss_list)
        # 暂停1秒，用于动态更新图形（常见于实时或迭代可视化）
        plt.pause(1)
plt.show()